import { triplitClient } from "@main/triplit/client";
import { CHAT_PARAMETERS_CACHE_KEY } from "@shared/constants";
import logger from "@shared/logger/main-logger";
import type {
  CreateThreadFormData,
  ThreadForm,
  ThreadFormChatParameters,
  UpdateThreadFormChatParametersData,
  UpdateThreadFormData,
} from "@shared/triplit/types";
import { injectable } from "inversify";
import { BaseDbService } from "./base-db-service";

@injectable()
export class ThreadFormsDbService extends BaseDbService {
  constructor() {
    super("threadForms");
  }

  async insertThreadForm(
    threadForm: CreateThreadFormData,
  ): Promise<ThreadForm> {
    try {
      return await triplitClient.insert("threadForms", threadForm);
    } catch (error) {
      logger.error("ThreadFormsDbService:insertThreadForm error", { error });
      throw error;
    }
  }

  async updateThreadForm(
    threadFormId: string,
    updateData: Omit<UpdateThreadFormData, "updatedAt">,
  ) {
    try {
      await triplitClient.update(
        "threadForms",
        threadFormId,
        async (threadForm) => {
          Object.assign(threadForm, updateData);
          threadForm.updatedAt = new Date();
        },
      );
    } catch (error) {
      logger.error("ThreadFormsDbService:updateThreadForm error", { error });
      throw error;
    }
  }

  async deleteThreadForm(threadFormId: string) {
    try {
      await triplitClient.delete("threadForms", threadFormId);
    } catch (error) {
      logger.error("ThreadFormsDbService:deleteThreadForm error", { error });
      throw error;
    }
  }

  async getThreadFormByThreadId(threadId: string): Promise<ThreadForm | null> {
    try {
      const query = triplitClient
        .query("threadForms")
        .Where("threadId", "=", threadId);
      const threadForm = await triplitClient.fetchOne(query);
      return threadForm;
    } catch (error) {
      logger.error("ThreadFormsDbService:getThreadFormByThreadId error", {
        error,
      });
      throw error;
    }
  }

  async getChatParametersByThreadId(
    threadId: string,
  ): Promise<ThreadFormChatParameters> {
    try {
      const query = triplitClient
        .query("threadForms")
        .Where("threadId", "=", threadId)
        .Select(["chatParameters"]);
      const chatParameters = await triplitClient.fetchOne(query);

      return (chatParameters?.chatParameters ??
        null) as ThreadFormChatParameters;
    } catch (error) {
      logger.error("ThreadFormsDbService:getChatParametersByThreadId error", {
        error,
      });
      throw error;
    }
  }

  async updateChatParametersByThreadId(
    threadId: string,
    updateData: UpdateThreadFormChatParametersData,
  ) {
    try {
      await triplitClient.transact(async (tx) => {
        const query = triplitClient
          .query("threadForms")
          .Where("threadId", "=", threadId);
        let threadForm = await tx.fetchOne(query);

        if (!threadForm) {
          threadForm = await tx.insert("threadForms", { threadId });
        }

        await tx.update("threadForms", threadForm.id, (form) => {
          Object.assign(form, updateData);
          form.systemPromptVariables = new Set([]);
          form.systemPromptRenderMap = {};
          form.systemPromptRenderedContent = "";
          form.updatedAt = new Date();
        });
      });
    } catch (error) {
      logger.error(
        "ThreadFormsDbService:updateChatParametersByThreadId error",
        {
          error,
          threadId,
        },
      );
      throw error;
    }
  }

  async deleteThreadFormsByThreadId(threadId: string) {
    try {
      const query = triplitClient
        .query("threadForms")
        .Where("threadId", "=", threadId);
      const threadForms = await triplitClient.fetchOne(query);
      if (threadForms) {
        await triplitClient.delete("threadForms", threadForms.id);
      }
    } catch (error) {
      logger.error("ThreadFormsDbService:deleteThreadFormsByThreadId error", {
        error,
      });
      throw error;
    }
  }

  async deleteAllThreadForms() {
    try {
      const query = triplitClient.query("threadForms");
      const threadForms = await triplitClient.fetch(query);
      await triplitClient.transact(async (tx) => {
        const deletePromises = threadForms.map((threadForm) =>
          tx.delete("threadForms", threadForm.id),
        );
        await Promise.all(deletePromises);
      });
    } catch (error) {
      logger.error("ThreadFormsDbService:deleteAllThreadForms error", {
        error,
      });
      throw error;
    }
  }

  private async resetCacheThreadForms(cachedFormId: string) {
    try {
      await this.updateThreadForm(cachedFormId, {
        chatInput: {},
        chatParameters: {},
      });
    } catch (error) {
      logger.error("ThreadFormsDbService:resetCacheThreadForms error", {
        error,
      });
      throw error;
    }
  }

  async updateChatParametersFromCache(threadId: string) {
    try {
      const cacheQuery = triplitClient
        .query("threadForms")
        .Where("threadId", "=", CHAT_PARAMETERS_CACHE_KEY);
      const cachedParams = await triplitClient.fetchOne(cacheQuery);

      await this.updateChatParametersByThreadId(threadId, {
        chatParameters: cachedParams?.chatParameters ?? {},
      });

      if (cachedParams?.id) {
        await this.resetCacheThreadForms(cachedParams.id);
      }
    } catch (error) {
      logger.error("ThreadFormsDbService:updateChatParametersFromCache error", {
        error,
      });
      throw error;
    }
  }

  async getThreadModelName(threadId: string) {
    try {
      const query = triplitClient
        .query("threadForms")
        .Where("threadId", "=", threadId)
        .Include("threads", (rel) => rel("threads").Include("model"));

      const threadForm = await triplitClient.fetchOne(query);
      return threadForm?.threads?.model?.name ?? "";
    } catch (error) {
      logger.error("ThreadFormsDbService:getThreadModelName error", {
        error,
      });
      throw error;
    }
  }

  async getSystemPromptParams(threadId: string): Promise<{
    systemPrompt: string;
    systemPromptRenderMap: JSON;
    systemPromptRenderedContent: string;
    systemPromptVariables: Set<string>;
  }> {
    try {
      const threadsQuery = triplitClient
        .query("threadForms")
        .Where("threadId", "=", threadId);
      const threadForm = await triplitClient.fetchOne(threadsQuery);

      if (!threadForm) {
        logger.error(
          "ThreadFormsDbService:getSystemPromptParams thread not found",
          {
            threadId,
          },
        );
        throw new Error("Thread not found");
      }

      return {
        systemPrompt: threadForm.chatParameters?.systemPrompt ?? "",
        systemPromptRenderMap: threadForm.systemPromptRenderMap,
        systemPromptRenderedContent:
          threadForm.systemPromptRenderedContent ?? "",
        systemPromptVariables: threadForm.systemPromptVariables ?? new Set(),
      };
    } catch (error) {
      logger.error("ThreadFormsDbService:getSystemPromptParams error", {
        error,
      });
      throw error;
    }
  }

  async getThreadById(threadId: string): Promise<ThreadForm | null> {
    try {
      const query = triplitClient
        .query("threadForms")
        .Where("threadId", "=", threadId)
        .Include("threads");
      const threadForm = await triplitClient.fetchOne(query);
      return threadForm;
    } catch (error) {
      logger.error("ThreadFormsDbService:getThreadById error", {
        error,
      });
      throw error;
    }
  }

  async updateSystemPromptParams(
    threadId: string,
    updateData: {
      // biome-ignore lint/suspicious/noExplicitAny: json type
      systemPromptRenderMap: any;
      systemPromptRenderedContent: string;
      systemPromptVariables: Set<string>;
    },
  ) {
    try {
      const threadFormQuery = triplitClient
        .query("threadForms")
        .Where("threadId", "=", threadId);
      const threadForm = await triplitClient.fetchOne(threadFormQuery);
      if (!threadForm) {
        logger.error("ThreadFormsDbService:updateSystemPromptParams error", {
          error: "Thread not found",
        });
        throw new Error("Thread not found");
      }
      await triplitClient.update("threadForms", threadForm.id, (form) => {
        Object.assign(form, updateData);
        form.updatedAt = new Date();
      });
    } catch (error) {
      logger.error("ThreadFormsDbService:updateSystemPromptParams error", {
        error,
      });
      throw error;
    }
  }
}
